
/**
 * @fileoverview Provides data persistence using HTML5 local storage
 * mechanism. Local storage must be available under window.localStorage,
 * see: http://www.w3.org/TR/webstorage/#the-localstorage-attribute.
 * @link http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml
 * @link https://developers.google.com/closure/compiler/docs/js-for-compiler
 */



/**
 * Provides a storage mechanism that uses HTML5 local storage.
 * @requires util.StringUtils.JSON
 * @constructor
 */
dom.DataStorage = function() {

  /**
   * Sets a value for a key.
   * @param {string} key The key to set.
   * @param {*} value The string to save.
   */
  this.set = function(key, value) {
    /** @type {string} */ var result = util.StringUtils.JSON.stringify(value);
    if (localStorage_) {
      localStorage_.setItem(key, result);
    } else if (globalStorage_) {
      globalStorage_[key] = result;
    } else if (userData_) {
      userData_.setAttribute(encodeURIComponent(key), result);
      userData_['save'](userData_.tagName);
    } else {
      data_[key] = result;
    }
  };

  /**
   * Gets the value stored under a key.
   * @param {string} key The key to get.
   * @return {?Object} The corresponding value, null if not found.
   */
  this.get = function(key) {
    var value = data_[key];
    if (localStorage_) {
      value = localStorage_.getItem(key);
    } else if (globalStorage_) {
      value = globalStorage_[key] && globalStorage_[key].value;
    } else if (userData_) {
      value = userData_.getAttribute(decodeURIComponent(key));
    }
    return util.StringUtils.JSON.parse(value);
  };

  /**
   * Removes a key and its value.
   * @param {string} key The key to remove.
   */
  this.remove = function(key) {
    if (localStorage_) {
      localStorage_.removeItem(key);
    } else if (globalStorage_) {
      delete globalStorage_[key];
    } else if (userData_) {
      userData_.removeAttribute(encodeURIComponent(key));
      userData_['save'](userData_.tagName);
    } else {
      delete data_[key];
    }
  };

  /**
   * Removes all key-value pairs.
   */
  this.clear = function() {
    if (localStorage_) {
      localStorage_.clear();
    } else if (globalStorage_) {
      for (/** @type {string} */ var key in globalStorage_) {
        self_.remove(key);
      }
    } else if (userData_) {
      for (/** @type {number} */ var i = 0; i < userData_.attributes.length;) {
        userData_.removeAttribute(userData_.attributes[i++].nodeName);
      }
      userData_['save'](userData_.tagName);
    } else {
      data_ = {};
    }
  };

  /**
   * Gets list of stored keys.
   * @return {!Array.<string>} Returns list of stored keys.
   */
  this.keys = function() {
    /** @type {!Array.<string>} */ var keys = [];
    if (userData_) {
      for (/** @type {number} */ var i = 0; i < userData_.attributes.length;) {
        keys.push(userData_.attributes[i++].nodeName);
      }
    } else {
      /** @type {?Object} */
      var storage = localStorage_ || globalStorage_ || data_;
      for (/** @type {string} */ var key in storage) {
        keys.push(key);
      }
    }
    keys.sort();
    return keys;
  };

  /**
   * The reference to current class instance. Used in private methods.
   * @type {!dom.DataStorage}
   * @private
   */
  var self_ = this;

  /**
   * @type {Object}
   * @private
   */
  var data_ = {};

  /**
   * @type {Object}
   * @private
   */
  var localStorage_ = (function() {
    /** @type {string} */ var key = 'localStorage';
    /** @preserveTry */
    try {
      return key in window && window[key];
    } catch (e) {
      return null;
    }
  })();

  /**
   * @type {Object}
   * @private
   */
  var globalStorage_ = (function() {
    /** @type {string} */ var key = 'globalStorage';
    /** @preserveTry */
    try {
      return key in window && window[key] && window[key][location.hostname];
    } catch (e) {
      return null;
    }
  })();

  /**
   * @type {Object}
   * @private
   */
  var userData_ = (function() {
    /** @preserveTry */
    try {
      var node = dom.createElement('userdata');
      node['addBehavior']('#default#userData');
      dom.document.body.appendChild(node);
      node['load'](node.tagName);
      return node;
    } catch (e) {
      return null;
    }
  })();
};
